Add logging capabilities to AIProjectClient and related samples - #48394
Conversation
- Introduced a new logging transport class (_OpenAILoggingTransport) for handling OpenAI requests and responses. - Enhanced AIProjectClient to support console logging and custom user agents. - Created utility functions for generating timestamped log files. - Added multiple sample scripts demonstrating logging configurations, including: - Capturing both Azure-core and OpenAI transport logs. - Writing logs to console and files with different logging levels. - Implemented unit tests for logging behavior in both synchronous and asynchronous contexts. - Updated test helpers to accommodate new logging features and configurations.
|
Azure Pipelines: Successfully started running 1 pipeline(s). 9 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Adds configurable logging for synchronous and asynchronous OpenAI clients created through AIProjectClient.
Changes:
- Adds dedicated HTTPX logging transports with configurable redaction.
- Adds console/file logging samples and utilities.
- Adds synchronous and asynchronous logging tests.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
tests/responses/test_openai_client_overrides.py |
Updates sync transport tests. |
tests/responses/test_openai_client_overrides_async.py |
Updates async transport tests. |
tests/responses/test_client_logging.py |
Tests sync logging behavior. |
tests/responses/test_client_logging_async.py |
Tests async logging behavior. |
tests/responses/openai_test_helpers.py |
Adds logging configuration to test clients. |
samples/logs/util.py |
Exposes shared sample helpers. |
samples/logs/log_utils.py |
Creates timestamped log paths. |
samples/logs/sample_log_with_logging_disabled.py |
Demonstrates reduced logging. |
samples/logs/sample_log_to_console.py |
Demonstrates console logging. |
samples/logs/sample_log_from_sdk.py |
Demonstrates Azure SDK logging. |
samples/logs/sample_log_from_openai_client.py |
Demonstrates OpenAI transport logging. |
samples/logs/sample_log_all.py |
Demonstrates combined logging. |
azure/ai/projects/_patch.py |
Implements synchronous logging transport and wiring. |
azure/ai/projects/_patch.pyi |
Adds synchronous logging type declarations. |
azure/ai/projects/aio/_patch.py |
Implements asynchronous logging transport. |
azure/ai/projects/aio/_patch.pyi |
Adds asynchronous logging type declarations. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.
Suppressed comments (3)
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:46
- This explicit
logging_enable=FalsepreventsNetworkTraceLoggingPolicyfrom emitting Azure-core HTTP traces, while the console-logging constructor setsazure.core.pipeline.policies.http_logging_policytoERROR. Consequently, this sample advertised as capturing both Azure-core and OpenAI HTTP logs only emits the OpenAI transport logs. Either enable network tracing here or preserve the redacted HTTP policy when full logging is disabled.
AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=False) as project_client,
sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:59
- The sample test suite discovers files only within folders explicitly passed to
get_sample_paths, andtests/samples/test_samples.pyhas no registration forlogs. As a result, none of the five new logging samples run in CI. Add a recorded sample test forget_sample_paths("logs", ...), explicitly skipping by filename only where recording is not possible.
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=True) as project_client,
sdk/ai/azure-ai-projects/.tmp_probe_openai_stream.py:1
- This is a one-off diagnostic probe that executes immediately on import, prints request details, and deliberately raises an exception; it is neither a package module nor a test/sample covered by the PR. Remove this temporary file before merging.
import httpx
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.
Suppressed comments (4)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:364
- When reduced logging is selected, this still writes the complete URL (including caller-supplied
default_queryvalues), and_sanitize_auth_headerleaves anapi-keyheader unchanged before this loop emits it. This contradicts the documented default redaction and can expose credentials or sensitive query parameters in ordinary debug logs. Redact query values and every credential-bearing header unless explicit body logging is enabled; authentication credentials should remain redacted in either mode.
_OPENAI_TRANSPORT_LOGGER.debug("\n==> Request:\n%s %s", request.method, request.url)
headers = dict(request.headers)
self._sanitize_auth_header(headers)
_OPENAI_TRANSPORT_LOGGER.debug("Headers:")
for key, value in sorted(headers.items()):
_OPENAI_TRANSPORT_LOGGER.debug(" %s: %s", key, value)
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:270
- The async path also logs the full URL and emits
api-keyunchanged whenlogging_enabled=False. Because callers may supply arbitrarydefault_queryvalues and headers, reduced logging can leak sensitive data. Redact URL query values and all credential-bearing headers in the async transport as well.
_OPENAI_TRANSPORT_LOGGER.debug("\n==> Request:\n%s %s", request.method, request.url)
headers = dict(request.headers)
self._sanitize_auth_header(headers)
_OPENAI_TRANSPORT_LOGGER.debug("Headers:")
for key, value in sorted(headers.items()):
_OPENAI_TRANSPORT_LOGGER.debug(" %s: %s", key, value)
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- This description says the console sample writes to a file, but the implementation only enables
AZURE_AI_PROJECTS_CONSOLE_LOGGINGand writes to the console. Update the description so users are not directed to expect a log file.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:26
- This adds user-visible OpenAI transport logging behavior, but
CHANGELOG.mdstill ends at 2.4.0 and contains no entry for the feature. Add a release-history entry describing the new logging behavior and samples so the significant SDK change is discoverable to users.
_OPENAI_TRANSPORT_LOGGER_NAME = "azure.ai.projects.openai_transport"
_OPENAI_TRANSPORT_LOGGER = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (4)
sdk/ai/azure-ai-projects/samples/logs/log_utils.py:11
- This annotation is evaluated when the module is imported, but
str | Pathrequires Python 3.10 while this package supports Python 3.9 (pyproject.toml:31). Running any of these samples on Python 3.9 will therefore fail while importinglog_utils; usetyping.Unionfor compatibility.
def create_timestamped_temp_log_file(script_path: str | Path) -> Path:
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:215
- Passing a plain
httpx.Clientchanges OpenAI's defaults for every generated client, including when logging is disabled. OpenAI 2.8 documents that customhttpx.Clientinstances use HTTPX defaults instead of its 600-second timeout, larger connection limits, and redirect handling; this can make existing long-running or redirected requests fail. Construct the logging client withopenai.DefaultHttpxClient(or explicitly preserve all OpenAI defaults).
return httpx.Client(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:143
- Passing a plain
httpx.AsyncClientchanges OpenAI's async defaults for every generated client, including when logging is disabled. OpenAI 2.8 documents that custom clients use HTTPX defaults instead of its 600-second timeout, larger connection limits, and redirect handling; this can make existing long-running or redirected requests fail. Construct the transport withopenai.DefaultAsyncHttpxClient(or explicitly preserve all OpenAI defaults).
return httpx.AsyncClient(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- The description says this console sample writes logs into a file, contradicting both its name and the later statement on line 25. Describe the console destination here so users do not expect a log file to be created.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
…e handlers for openai_transport
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
Suppressed comments (9)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:378
- This only treats SSE as streaming. The returned OpenAI client also exposes
with_streaming_responsefor non-SSE responses (for example file downloads withapplication/octet-stream), and those responses still take theresponse.read()branch and are fully buffered before the caller sees them. Preserve the response stream for every streaming request rather than inferring streaming solely fromContent-Type.
if self._is_streaming_response(response):
_OPENAI_TRANSPORT_LOGGER.debug("Body: [Streaming response not logged]")
else:
content = response.read()
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:364
- The sanitizer's contract includes
api-key, but it only rewritesauthorization. Becauseget_openai_clientaccepts caller-provideddefault_headers, anapi-keyheader is logged verbatim here even withlogging_enable=False. Redactapi-key(case-insensitively) in reduced mode before iterating the headers.
_OPENAI_TRANSPORT_LOGGER.debug("Headers:")
for key, value in sorted(headers.items()):
_OPENAI_TRANSPORT_LOGGER.debug(" %s: %s", key, value)
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:270
- The async sanitizer says it handles
api-key, but it only redactsauthorization. A caller-supplieddefault_headers={"api-key": ...}value is therefore emitted unchanged here under reduced logging. Redactapi-keycase-insensitively wheneverlogging_enable=False.
_OPENAI_TRANSPORT_LOGGER.debug("Headers:")
for key, value in sorted(headers.items()):
_OPENAI_TRANSPORT_LOGGER.debug(" %s: %s", key, value)
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:284
- This only protects SSE streams. Async OpenAI's
with_streaming_responsecan stream non-SSE payloads such as file downloads, which still reachresponse.aread()here and are fully buffered before being returned. Preserve the async response stream for all streaming requests instead of using only the response content type as the signal.
if self._is_streaming_response(response):
_OPENAI_TRANSPORT_LOGGER.debug("Body: [Streaming response not logged]")
else:
content = await response.aread()
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:215
- Supplying a plain
httpx.Clientreplaces OpenAI's own default HTTP client even when logging is disabled. In particular, httpx defaultsfollow_redirectsto false while OpenAI's default client enables it, so redirects that previously succeeded can now be returned as errors; other OpenAI connection defaults are also bypassed. Build this around OpenAI's default client configuration (or reproduce all of its defaults) when installing the transport.
logging_kwargs = getattr(self, "_kwargs", {})
logging_enabled = bool(logging_kwargs.get("logging_enable", False))
return httpx.Client(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:143
- This replaces OpenAI's default async HTTP client for every caller, including
logging_enable=False. A plainhttpx.AsyncClientdoes not preserve OpenAI's defaults (notablyfollow_redirects=True), so redirected requests can regress and other connection settings may change. Use OpenAI's default async client configuration while injecting this transport.
logging_kwargs = getattr(self, "_kwargs", {})
logging_enabled = bool(logging_kwargs.get("logging_enable", False))
return httpx.AsyncClient(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))
sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:59
- None of the five new
samples/logs/sample_*.pyfiles is registered intests/samples/test_samples.py. That suite discovers samples only within folders explicitly passed toget_sample_paths, and there is currently nologsentry, so these samples—including their local utility imports and logger setup—will never execute in CI. Add alogssample parametrization, or explicitly skip individual filenames with a reason if they cannot be recorded.
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=True) as project_client,
sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:323
- The capture handler is attached both to the root logger and directly to this child logger, but the transport logger normally has
propagate=True. Every OpenAI transport record is therefore appended toprint_callstwice in ordinary samples, inflating and duplicating the text sent to LLM validation. Temporarily disable propagation while the direct handler is installed, then restore the prior value.
directly_attached_loggers = []
for logger_name in ("azure.ai.projects.openai_transport",):
logger_instance = logging.getLogger(logger_name)
logger_instance.addHandler(capture_handler)
directly_attached_loggers.append(logger_instance)
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- The description says this console sample writes logs into a file, contradicting both its name and the usage note below. Describe the console destination so users do not look for a log file that is never created.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.
Suppressed comments (4)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:393
- The OpenAI client’s
limits=DEFAULT_CONNECTION_LIMITSsetting does not configure a caller-supplied transport—HTTPX returns that transport unchanged. Because this transport initializesHTTPTransportwith its own defaults, all default OpenAI clients now use HTTPX’s smaller 100/20 connection pool instead of OpenAI’s 1000/100 pool, which can introduce connection-pool contention under concurrency. Initialize both custom transports with OpenAI’s default connection limits.
super().__init__()
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:238
DefaultAsyncHttpxClientcannot apply itsDEFAULT_CONNECTION_LIMITSto this supplied transport because HTTPX uses a custom transport unchanged. Thissuper().__init__()therefore reduces the async OpenAI pool from OpenAI’s 1000/100 defaults to HTTPX’s 100/20 defaults, potentially throttling concurrent workloads. Initialize both custom transports with OpenAI’s default connection limits.
super().__init__()
sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:340
- Removing the environment-variable check makes the shipped
SAMPLE_TEST_ERROR_LOG,SAMPLE_TEST_FAILED_LOG, andSAMPLE_TEST_PASSED_LOGsettings in `.env.template:101-107 dead configuration, although that file still says uncommenting them enables logging. Update/remove those settings and comments, or retain the gating, so users are not given ineffective configuration.
def _build_live_log_file_path(self, suffix: str) -> Optional[str]:
"""Build a live-mode sample log path in the system temp directory."""
# Only create logs in live mode
if not _is_live_mode():
return None
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- This description contradicts the sample: no file handler is created, and line 39 enables console logging, which causes the client to default
logging_enabletoTrue, notFalse. Describe console output and the console redaction behavior instead.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.
Suppressed comments (7)
sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:456
- The new “print output” log is not faithful to
print(): capture discardssepandend, and this writer appends a newline to every call. For the added streaming samples,print(event.delta, end="")is therefore rewritten as one line per delta. Preserve each call's rendered separator/terminator (or capture into a text buffer) and write it verbatim; update the CLI executor overrides as well.
if self.print_output_calls:
for print_call in self.print_output_calls:
file_handle.write(f"{print_call}\n")
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:233
- Each client constructed with
AZURE_AI_PROJECTS_CONSOLE_LOGGING=truecreates and permanently attaches another handler to this process-global logger. Creating two clients therefore emits every OpenAI transport record twice, and closing either client does not restore the logger. Make this setup idempotent or manage the handler's lifetime explicitly; the async constructor must use the same shared strategy.
openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
openai_transport_logger.setLevel(logging.DEBUG)
openai_transport_logger.propagate = False
openai_transport_logger.addHandler(console_handler)
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:113
- This async constructor also adds a fresh handler to the same global transport logger on every client creation. Mixing sync/async clients or constructing multiple async clients duplicates each log record and leaves handlers behind after clients close. Use the same idempotent, lifecycle-managed logger configuration as the sync path.
openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
openai_transport_logger.setLevel(logging.DEBUG)
openai_transport_logger.propagate = False
openai_transport_logger.addHandler(console_handler)
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- This description contradicts the sample: it configures console output, not a file, and the environment flag causes
logging_enableto default toTrue, notFalse. Describe the actual console-logging behavior so users do not expect reduced file logging.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
sdk/ai/azure-ai-projects/samples/logs/sample_log_stream_events.py:12
- The transport now wraps SSE streams and logs every raw response chunk when
logging_enable=True, so the statement that streamed events are not written to SDK logs is incorrect. Clarify that raw stream chunks go to the log while parsed events are printed to the console.
With logging_enable=True, request bodies, response metadata, and token are
included in the log file. Streamed response events are printed to the
console and are not automatically written to SDK logs.
sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:59
- None of the new
samples/logs/sample_*.pyscripts are collected:tests/samples/test_samples.pyregisters folders through explicitget_sample_paths(...)calls, and there is nologsentry. Add sync/async sample coverage for this folder, using exact-filename skips only for scripts that cannot run under recorded tests.
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=True) as project_client,
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:384
- This adds a user-visible logging transport and changes the default
get_openai_client()HTTP-client behavior, but the PR has no CHANGELOG entry. The package records user-visible features underCHANGELOG.md“Features Added” (for example lines 5–16); add an entry for this feature before release.
class _OpenAILoggingTransport(httpx.HTTPTransport):
"""Custom HTTP transport that logs OpenAI API requests and responses.
This transport wraps httpx.HTTPTransport to intercept all HTTP traffic and emit
detailed request/response information through a dedicated logger. It automatically
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.
Suppressed comments (4)
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- This description contradicts the sample: it writes to the console, not a file, and setting
AZURE_AI_PROJECTS_CONSOLE_LOGGING=truemakes the client defaultlogging_enabletoTrue, so request and response bodies are included rather than excluded. Update the description to match the demonstrated console/full-logging behavior.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:9
- None of the new
samples/logs/sample_*.pyscripts are exercised by the package's sample suite.get_sample_pathsdiscovers samples only within folders explicitly parameterized intests/samples/test_samples.py, and that file has nologsentry. Add sync/async sample coverage (or explicit skips with reasons) so these executable examples are validated like the other sample folders.
"""
DESCRIPTION:
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:456
- The new print-only log does not preserve actual
print()semantics:_capture_printdiscardssep/end, and this loop then appends a newline after every call. For example,sample_log_stream_events*.pyusesprint(event.delta, end=""), but its output log will put every streamed delta on a separate line. Capture each call with its effectivesepandendand write those fragments verbatim; the sync and async CLI overrides need the same treatment.
for print_call in self.print_output_calls:
file_handle.write(f"{print_call}\n")
sdk/ai/azure-ai-projects/samples/logs/sample_log_stream_events.py:12
- With full logging enabled, the new transport wrapper logs each SSE body chunk lazily as it is consumed, so streamed response data is automatically written to this log file. The sample description currently says the opposite.
With logging_enable=True, request bodies, response metadata, and token are
included in the log file. Streamed response events are printed to the
console and are not automatically written to SDK logs.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.
Suppressed comments (4)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:233
- This handler is added to a process-global logger for every client construction and is never removed by
AIProjectClient.close(). Creating two console-logging clients causes every OpenAI transport record to be emitted twice, and the duplication continues after either client closes. Make this setup idempotent or retain and remove the client-owned handler during close.
openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
openai_transport_logger.setLevel(logging.DEBUG)
openai_transport_logger.propagate = False
openai_transport_logger.addHandler(console_handler)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:384
- This adds user-visible logging behavior and configuration, but the package
CHANGELOG.mdhas no corresponding entry. Add the feature under the next release so consumers can discover the new transport logger and console/file logging behavior.
class _OpenAILoggingTransport(httpx.HTTPTransport):
"""Custom HTTP transport that logs OpenAI API requests and responses.
This transport wraps httpx.HTTPTransport to intercept all HTTP traffic and emit
detailed request/response information through a dedicated logger. It automatically
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:113
- The async constructor also appends a new handler to the global transport logger on every client creation without removing it in
close(). Multiple async clients therefore multiply each log line and leave the handlers installed after their contexts exit. Make handler installation idempotent or clean up the client-owned handler on close.
openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
openai_transport_logger.setLevel(logging.DEBUG)
openai_transport_logger.propagate = False
openai_transport_logger.addHandler(console_handler)
sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:59
- None of the new
samples/logsfiles are registered intest_samples.pyortest_samples_async.py. Sinceget_sample_pathsonly discovers files inside explicitly requested folders, these samples are never imported or executed in CI. Add sync/async sample test entries for this folder, with recordings or explicit skips where required.
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=True) as project_client,
[Pilot] PR Pipeline Failure AnalysisA CI pipeline failed on this pull request. Here is an automated analysis of what went wrong and how to get the build green. What failedThe
The same set of failures repeats identically across platforms and both Recommended next steps
Raw pipeline analysis (azsdk ci analyze)
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.
Suppressed comments (6)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:26
_openai_transport_loggerrcontains a typo and is used throughout this module. Rename it to_openai_transport_loggerconsistently so the sync and async implementations use the same clear name.
_openai_transport_loggerr = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:233
- Each console-enabled client appends a new handler to this process-global logger, and client shutdown never removes it. Constructing two clients therefore emits every OpenAI transport record twice; repeated short-lived clients (including the sample executor) keep increasing output and retain stale
sys.stdoutstreams. Install/reuse one process-level handler or remove the client-owned handler during shutdown.
openai_transport_logger.addHandler(console_handler)
sdk/ai/azure-ai-projects/samples/logs/sample_log_stream_events.py:12
- This says streamed responses are not written to SDK logs, but the new full-logging transport wraps the SSE stream and logs every raw chunk as it is consumed (
_LoggingSyncByteStream), as the new transport test also asserts. Clarify that raw chunks are logged while parsed events are printed.
With logging_enable=True, request bodies, response metadata, and token are
included in the log file. Streamed response events are printed to the
console and are not automatically written to SDK logs.
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:113
- The async constructor also adds a fresh handler to the same global transport logger on every client creation without removing it. Multiple async clients—or a sync and async client in one process—will duplicate every transport log and retain old stdout streams. Reuse a single configured handler or remove client-owned handlers when the client closes.
openai_transport_logger.addHandler(console_handler)
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- The description contradicts the sample: it says logs go to a file with
logging_enable=False, but the code setsAZURE_AI_PROJECTS_CONSOLE_LOGGING=trueand writes to the console. Update the description so users understand the behavior being demonstrated.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:456
- This does not preserve actual
print()output:_capture_printdiscardssep/end, and this loop then adds a newline after every call. For example, the new streaming sample'sprint(event.delta, end="")will be written one chunk per line instead of as the displayed text. Capture the rendered fragments with theirsepandendvalues and write them verbatim; update the CLI executor overrides similarly.
if self.print_output_calls:
for print_call in self.print_output_calls:
file_handle.write(f"{print_call}\n")
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.
Suppressed comments (13)
sdk/ai/azure-ai-projects/tests/samples/llm-analyze.py:170
- The async CLI override drops
sepandendtoo, causing its newprint_output_fileto insert line breaks between streamed deltas that were printed withend="". Preserve the print formatting and have the shared writer emit it verbatim.
def _capture_print(self, *args, **_kwargs):
text = " ".join(str(arg) for arg in args)
self.print_calls.append(text)
self.print_output_calls.append(text)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:149
- This async
finallymessage also labels failed or partially consumed streams as completed. Emit completion only when async iteration exhausts normally, and log/re-raise iteration failures separately so the new transport logs remain accurate.
finally:
_openai_transport_logger.debug("Body: [Streaming response completed]")
sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:325
- This handler is attached both here and to the root logger. When
openai_transporthas its defaultpropagate=True, each record reaches the same handler twice, soprint_callsand the generated reports contain duplicate OpenAI request/response entries. Temporarily disable propagation while the direct handler is installed, then restore it during cleanup.
logger_instance.addHandler(capture_handler)
directly_attached_loggers.append(logger_instance)
sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:239
print_output_callsis described and persisted as the captured print output, but this normalization ignoressepandend. Samples such assamples/responses/sample_responses_stream_events.py:57useend="", so the output log writes every streamed delta on a separate line instead of reproducing the console output. Preserve each call's actualsep/endwhen building the output-only log.
text = " ".join(str(arg) for arg in args)
self.print_calls.append(text)
self.print_output_calls.append(text)
sdk/ai/azure-ai-projects/tests/samples/llm-analyze.py:92
- This override also drops
sepandend, so the CLI's newprint_output_filedoes not contain the actual printed output for streaming samples that useend=""; each token is later written on its own line. Preserve print formatting here as well as in the output-log writer.
This issue also appears on line 167 of the same file.
def _capture_print(self, *args, **_kwargs):
text = " ".join(str(arg) for arg in args)
self.print_calls.append(text)
self.print_output_calls.append(text)
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- The description contradicts the sample: it writes to the console, and
AZURE_AI_PROJECTS_CONSOLE_LOGGING=truemakes the constructor setlogging_enable=True, not false. Update this text so users are not told that bodies are omitted when this sample enables detailed logging.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
sdk/ai/azure-ai-projects/samples/logs/sample_log_stream_events.py:12
- The transport now wraps SSE streams and logs every raw body chunk as it is consumed (
_LoggingSyncByteStream), so the statement that streamed content is not written to SDK logs is incorrect. Clarify that parsed events go to the console while raw stream chunks are also written to this log file.
With logging_enable=True, request bodies, response metadata, and token are
included in the log file. Streamed response events are printed to the
console and are not automatically written to SDK logs.
sdk/ai/azure-ai-projects/samples/logs/sample_log_stream_events_async.py:12
- The async logging transport wraps SSE responses with
_LoggingAsyncByteStream, which writes every consumed raw chunk to the SDK log. This description currently promises the opposite; distinguish the logged raw stream chunks from the parsed events printed to the console.
operation. With logging_enable=True, request bodies, response metadata, and
token are included in the log file. Streamed response events are printed to
the console and are not automatically written to SDK logs as parsed events.
sdk/ai/azure-ai-projects/tests/responses/test_client_logging_async.py:160
- This test attaches to the module logger, whereas the sync implementation and the README direct users to
azure.ai.projects.openai_transport. The async implementation still emits its client-creation message through the module logger, so an async user following the documented dedicated-logger setup silently misses that event. Emit the async creation message through_openai_transport_loggerand update this test to attach to the documented logger.
handler = _attach_file_handler("azure.ai.projects.aio._patch", log_file)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:233
- Each client construction adds a new handler to this process-global logger, and closing the client never removes it. Creating two console-enabled clients therefore duplicates every OpenAI transport log (and retains both clients' handlers indefinitely). Configure this logger idempotently or track and remove the handler during client shutdown.
openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
openai_transport_logger.setLevel(logging.DEBUG)
openai_transport_logger.propagate = False
openai_transport_logger.addHandler(console_handler)
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:113
- The async constructor also appends a handler to the shared
azure.ai.projects.openai_transportlogger on every client creation without removing it. Multiple sync/async clients then produce duplicate records and retain stale handlers. Make setup idempotent or remove each client-owned handler on close.
openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
openai_transport_logger.setLevel(logging.DEBUG)
openai_transport_logger.propagate = False
openai_transport_logger.addHandler(console_handler)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:271
- This adds user-visible logging behavior to
AIProjectClient, butCHANGELOG.mdstill ends at 2.4.0 and contains no entry for it. The contribution checklist requires significant features to be recorded; add a Features Added entry (and the new logging samples) for the upcoming release.
logging_kwargs = getattr(self, "_kwargs", {})
logging_enabled = bool(logging_kwargs.get("logging_enable", False))
return DefaultHttpxClient(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:131
- The
finallyblock reports a completed stream even when the underlying iterator raises or the consumer stops early, which makes failure diagnostics falsely look successful. Log completion only after normal exhaustion; on an iteration error, log that the stream was interrupted and re-raise it.
This issue also appears on line 148 of the same file.
finally:
_openai_transport_logger.debug("Body: [Streaming response completed]")
Description
Please add an informative description that covers that changes made by the pull request and link all relevant issues.
If an SDK is being regenerated based on a new API spec, a link to the pull request containing these API spec changes should be included above.
All SDK Contribution checklist:
General Guidelines and Best Practices
Testing Guidelines